﻿using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Queue;
using Microsoft.WindowsAzure.Storage.Table;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Text.RegularExpressions;
using Webservice.Entities;
using Webservice.Models;

namespace Webservice
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "StorageService" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select StorageService.svc or StorageService.svc.cs at the Solution Explorer and start debugging.
    public class StorageService : IStorageService
    {
        static private string STORAGE_CONNECTION_STRING = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

        private CloudTable rawdataTable;
        private CloudQueue reportQueue;
        private CloudBlobContainer reportContainer;

        public string GetReport(string reference)
        {
            var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(STORAGE_CONNECTION_STRING);
            var blobClient = storageAccount.CreateCloudBlobClient();

            CloudBlobContainer reportContainer = blobClient.GetContainerReference("reports");
            reportContainer.CreateIfNotExists();

            CloudBlockBlob blobItem = reportContainer.GetBlockBlobReference(reference);

            if (!blobItem.Exists())
                throw new System.IO.FileNotFoundException();

            string contents = "";
            using(StreamReader reader = new StreamReader(blobItem.OpenRead()))
            {
                contents = reader.ReadToEnd();
            }

            return contents;
        }

        public List<string> GetMoments()
        {
            var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(STORAGE_CONNECTION_STRING);
            var tableClient = storageAccount.CreateCloudTableClient();
            var queueClient = storageAccount.CreateCloudQueueClient();

            rawdataTable = tableClient.GetTableReference("moments");
            rawdataTable.CreateIfNotExists();
            reportQueue = queueClient.GetQueueReference("vehiclereportqueue");
            reportQueue.CreateIfNotExists();

            CloudQueueMessage msg = reportQueue.GetMessage();
            var messageParts = msg.AsString.Split(new char[] { ',' });
            var ReportGUID = messageParts[0];

            TableQuery<MomentEntity> query = new TableQuery<MomentEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, ReportGUID));

            List<string> moments = new List<string>();
            
            foreach (MomentEntity entity in rawdataTable.ExecuteQuery(query))
            {
                moments.Add(JsonConvert.SerializeObject(entity));
            }
            
            return moments;
        }

        public List<Contracts.Report> GetReports()
        {
            var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(STORAGE_CONNECTION_STRING);
            var blobClient = storageAccount.CreateCloudBlobClient();
            
            CloudBlobContainer reportContainer = blobClient.GetContainerReference("reports");
            reportContainer.CreateIfNotExists();

            List<Contracts.Report> reports = new List<Contracts.Report>();
            foreach (CloudBlockBlob blobItem in reportContainer.ListBlobs())
            {
                blobItem.FetchAttributes();
                Contracts.Report report = new Contracts.Report()
                {
                    Grade = blobItem.Metadata["grade"],
                    Points = blobItem.Metadata["points"],
                    VIN = blobItem.Metadata["VIN"],
                    Infractions = (blobItem.Metadata["infractions"] != "NaN") ? int.Parse(blobItem.Metadata["infractions"]) : 0,
                    Name = blobItem.Name,
                    Modified = (DateTimeOffset)blobItem.Properties.LastModified,
                };
                
                reports.Add(report);
            }

            return reports;
        }

        public void UploadLog(string content)
        {
            try
            {
                this.StoreData(content);
            }
            catch (System.FormatException ex)
            {
                Debug.Print("An exception was caught formatting the data. {0}", ex.Message);
                throw;
            }
        }

        public StorageService()
        {
            var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(STORAGE_CONNECTION_STRING);
            var blobClient = storageAccount.CreateCloudBlobClient();
            var tableClient = storageAccount.CreateCloudTableClient();
            var queueClient = storageAccount.CreateCloudQueueClient();

            rawdataTable = tableClient.GetTableReference("moments");
            rawdataTable.CreateIfNotExists();

            reportQueue = queueClient.GetQueueReference("vehiclereportqueue");
            reportQueue.CreateIfNotExists();

            reportContainer = blobClient.GetContainerReference("reports");
            reportContainer.CreateIfNotExists();
        }

        #region Private Methods

        private void ValidateFormat(string[] fields)
        {
            string[] validFields = { "Created", "VIN", "Speed", "XAxis", "YAxis", "ZAxis", "Latitude", "Longitude", "Heading" };

            foreach (string field in validFields)
            {
                if (!fields.Contains(field))
                {
                    throw new System.ArgumentException("CSV is missing header field '" + field + "'");
                }
            }
        }

        private void StoreData(string contents)
        {
            ReportRequestEntity newReport = new ReportRequestEntity()
            {
                ReportGUID = Guid.NewGuid().ToString("N"),
                RowKey = DateTime.Now.ToString()
            };

            List<MomentModel> models = JsonConvert.DeserializeObject<List<MomentModel>>(contents);

            int row = 0;
            TableBatchOperation batchOperation = new TableBatchOperation();
            foreach (MomentModel model in models)
            {
                MomentEntity moment = new MomentEntity(newReport.ReportGUID, row++, model);
                
                if (newReport.VIN == null && moment.VIN.Length == 17)
                    newReport.VIN = moment.VIN;
        
                batchOperation.Insert(moment);
                if (batchOperation.Count == 50)
                {
                    try
                    {
                        rawdataTable.ExecuteBatch(batchOperation);
                    }
                    catch (StorageException ex)
                    {
                        Debug.Print("An exception occurred performing Azure Batch Table Operation. {0}", ex.Message);       
                    }
                    batchOperation.Clear();
                }
            }

            try
            {
                if(batchOperation.Count > 0)
                    rawdataTable.ExecuteBatch(batchOperation);
            }
            catch (StorageException ex)
            {
                Debug.Print("An exception occurred performing Azure Batch Table Operation. {0}", ex.Message);
            }

            reportQueue.AddMessage(new CloudQueueMessage(newReport.ReportGUID + "," + newReport.VIN));
        }

        /*
        private void StoreData(string csvstring)
        {
            ReportRequestEntity newReport = new ReportRequestEntity()
            {
                ReportGUID = Guid.NewGuid().ToString("N"),
                RowKey = DateTime.Now.ToString()
            };

            TableBatchOperation batchOperation = new TableBatchOperation();

            ICsvParser csvParser = new CsvParser(new StringReader(csvstring)); /// Opens previously uploaded file and parses 
            CsvReader csvReader = new CsvReader(csvParser); // Creates reader to read parsed CSV file

            string[] headers = { }; // We will hold our CSV headers here

            int row = 0;
            while (csvReader.Read()) // Begin reading line by line
            {
                MomentEntity moment = new Entities.MomentEntity()
                {
                    Entry = row++,
                    PartitionKey = newReport.ReportGUID
                };

                headers = csvReader.FieldHeaders; // If this a header line, grab the headers

                try
                {
                    this.ValidateFormat(headers);
                }
                catch (System.ArgumentException)
                {
                    throw;
                }

                for (int j = 0; j < headers.Count(); j++) // Loop through each field and save property value to snapshot
                {
                    string propertyName = headers[j]; // Property name of the snapshot property, must be the same as the header field
                    PropertyInfo propertyInfo = moment.GetType().GetProperty(propertyName);
                    if (propertyInfo != null)
                    {
                        object value = csvReader.GetField(j); // Gets the value of the current field

                        #region Identify and set properties

                        switch (propertyName)
                        {
                            case "VIN":
                                {
                                    Regex alphanumeric = new Regex("[^A-Z0-9]");
                                    Regex vinformat = new Regex("[IOQ]");

                                    value = alphanumeric.Replace((string)value, "");
                                    value = vinformat.Replace((string)value, "");

                                    break;
                                }
                            case "XAxis":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (System.Exception)
                                    {
                                        Debug.Print("Could not parse X Acceleration (" + value + ")");
                                    }

                                    break;
                                }
                            case "YAxis":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (System.Exception)
                                    {
                                        Debug.Print("Could not parse Y Acceleration (" + value + ")");
                                    }

                                    break;
                                }
                            case "ZAxis":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (System.Exception)
                                    {
                                        Debug.Print("Could not parse Z Acceleration (" + value + ")");
                                    }

                                    break;
                                }
                            case "Speed":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (SystemException)
                                    {
                                        Debug.Print("Could not parse Speed (" + value + ")");
                                    }
                                    break;
                                }
                            case "Heading":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (SystemException)
                                    {
                                        Debug.Print("Could not parse Heading (" + value + ")");
                                    }
                                    break;
                                }
                            case "Latitude":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (SystemException)
                                    {
                                        Debug.Print("Could not parse Latitude (" + value + ")");
                                    }

                                    break;
                                }
                            case "Longitude":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (SystemException)
                                    {
                                        Debug.Print("Could not parse Longitude (" + value + ")");
                                    }

                                    break;
                                }
                            case "Fuel":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (SystemException)
                                    {
                                        Debug.Print("Could not parse Fuel (" + value + ")");
                                    }

                                    break;
                                }
                            case "Throttle":
                                {
                                    try
                                    {
                                        value = double.Parse((string)value);
                                    }
                                    catch (SystemException)
                                    {
                                        Debug.Print("Could not parse Throttle (" + value + ")");
                                    }

                                    break;
                                }
                        }
                        propertyInfo.SetValue(moment, value, null);

                        #endregion
                    }
                }

                if (newReport.VIN == null && moment.VIN.Length == 17)
                    newReport.VIN = moment.VIN;

                batchOperation.Insert(moment);
                if (batchOperation.Count == 50)
                {
                    rawdataTable.ExecuteBatch(batchOperation);
                    batchOperation.Clear();
                }
            }

            rawdataTable.ExecuteBatch(batchOperation);
            reportQueue.AddMessage(new CloudQueueMessage(newReport.ReportGUID + "," + newReport.VIN));
        }
        */
        #endregion
    }
}
